package com.dzm.xgb.core.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.dzm.common.exception.Assert;
import com.dzm.common.result.ResponseEnum;
import com.dzm.xgb.core.enums.LendStatusEnum;
import com.dzm.xgb.core.enums.TransTypeEnum;
import com.dzm.xgb.core.factory.ReturnHandlerFactory;
import com.dzm.xgb.core.hfb.HfbConst;
import com.dzm.xgb.core.hfb.RequestHelper;
import com.dzm.xgb.core.pojo.entity.*;
import com.dzm.xgb.core.mapper.LendMapper;
import com.dzm.xgb.core.pojo.vo.BorrowInfoApprovalVO;
import com.dzm.xgb.core.pojo.vo.BorrowerDetailVO;
import com.dzm.xgb.core.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dzm.xgb.core.strategy.ReturnHandler;
import com.dzm.xgb.core.utils.LendNoUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * <p>
 * 标的准备表 服务实现类
 * </p>
 *
 * @author Dzm
 * @since 2024-08-27
 */
@Service
public class LendServiceImpl extends ServiceImpl<LendMapper, Lend> implements LendService {

    @Autowired
    private BorrowerService borrowerService;

    @Autowired
    private UserAccountService userAccountService;

    @Autowired
    private UserInfoService userInfoService;

    @Autowired
    private LendItemService lendItemService;

    @Autowired
    private LendReturnService lendReturnService;

    @Autowired
    private LendItemReturnService lendItemReturnService;

    @Override
    public void createLend(BorrowInfoApprovalVO vo, BorrowInfo borrowInfo) {
        // 构造标的对象
        Lend lend = new Lend();
        lend.setTitle(vo.getTitle());
        lend.setLendNo(LendNoUtils.getLendNo());
        lend.setAmount(borrowInfo.getAmount());
        lend.setBorrowInfoId(borrowInfo.getId());
        lend.setUserId(borrowInfo.getUserId());
        lend.setStatus(LendStatusEnum.INVEST_RUN.getStatus());
        lend.setReturnMethod(borrowInfo.getReturnMethod());
        lend.setLowestAmount(new BigDecimal(100));
        //审核用户id
        lend.setCheckAdminId(1L);
        //审核时间
        lend.setCheckTime(LocalDateTime.now());
        //起始时间
        String lendStartDateStr = vo.getLendStartDate();
        LocalDate lendStartDate = LocalDate.parse(lendStartDateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        lend.setLendStartDate(lendStartDate);
        //标的期数
        Integer period = borrowInfo.getPeriod();
        lend.setPeriod(period);
        //结束时间
        LocalDate lendEndDate = lendStartDate.plusMonths(period);
        lend.setLendEndDate(lendEndDate);
        //年利化率
        lend.setLendYearRate(vo.getLendYearRate().divide(new BigDecimal(100)));
        //服务费率
        lend.setServiceRate(vo.getServiceRate().divide(new BigDecimal(100)));

        //平台预期收益
        //计算月年化
        BigDecimal monthRate = lend.getServiceRate().divide(new BigDecimal(12), 8, BigDecimal.ROUND_DOWN);
        lend.setExpectAmount(lend.getAmount().multiply(monthRate).multiply(new BigDecimal(period)));
        //描述信息
        lend.setLendInfo(vo.getLendInfo());
        //初始值
        lend.setInvestAmount(new BigDecimal(0));
        lend.setInvestNum(0);
        lend.setRealAmount(new BigDecimal(0));
        //发布日期
        lend.setPublishDate(LocalDateTime.now());

        this.save(lend);
    }

    /**
     * 标的列表
     * @return
     */
    @Override
    public List<Lend> getList() {
        List<Lend> list = this.list();

        for (Lend lend : list) {
            Map<String, Object> map = new HashMap<>();
            String returnMethod = borrowerService.getDictNameByDictCodeAndValue("returnMethod", lend.getReturnMethod().toString());
            map.put("returnMethod", returnMethod);
            map.put("status", LendStatusEnum.getMsgByStatus(lend.getStatus()));

            lend.setParam(map);
        }
        return list;
    }

    /**
     * 根据标的id获取标的与借款人详细信息
     * @param id
     * @return
     */
    @Override
    public Map<String, Object> show(Long id) {
        // 标的信息
        Lend lend = this.getById(id);
        HashMap<String, Object> map = new HashMap<>();
        String returnMethod = borrowerService.getDictNameByDictCodeAndValue("returnMethod", lend.getReturnMethod().toString());
        map.put("returnMethod", returnMethod);
        map.put("status", LendStatusEnum.getMsgByStatus(lend.getStatus()));
        lend.setParam(map);

        //借款人信息
        Long userId = lend.getUserId();
        Borrower borrower = borrowerService.getOne(new LambdaQueryWrapper<Borrower>()
                .eq(Borrower::getUserId, userId));
        BorrowerDetailVO borrowerDetailVO = borrowerService.getDetailVo(borrower.getId());

        //将标的信息及借款人信息放入map集合
        Map<String, Object> result = new HashMap<>();
        result.put("lend", lend);
        result.put("borrower", borrowerDetailVO);

        return result;
    }

    @Override
    public List<Lend> findAll() {
        List<Lend> list = this.list();

        for (Lend lend : list) {
            Map<String, Object> map = new HashMap<>();
            String returnMethod = borrowerService.getDictNameByDictCodeAndValue("returnMethod", lend.getReturnMethod().toString());
            map.put("returnMethod", returnMethod);
            map.put("status", LendStatusEnum.getMsgByStatus(lend.getStatus()));

            lend.setParam(map);
        }

        return list;
    }

    @Override
    public BigDecimal getInterestCount(BigDecimal invest, BigDecimal yearRate, Integer totalmonth, Integer returnMethod) {
        //根据还款方式获取还款对象
        ReturnHandler returnHandler = ReturnHandlerFactory.getReturnHandler(returnMethod.toString());
        BigDecimal interestCount = returnHandler.getInterestCount(invest, yearRate, totalmonth);

        return interestCount;
    }

    /**
     * 放款
     * 1. 向第三方资金托管平台发送放款请求并获取返回数据
     * 2. 修改标的状态
     * 3. 修改借款人的账户信息
     * 4. 向交易流水表添加借款人交易流水信息
     * 5. 根据标的id查询状态为已支付的投资项
     * 6. 在投资项列表中修改投资人的账户信息
     * 7. 在投资人项列表中保存投资人交易流水
     * @param id
     */

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void makeLoan(Long id) {
        //判断是否满标
        Lend lend = this.getById(id);
        Assert.isTrue(lend.getAmount().equals(lend.getInvestAmount()), ResponseEnum.LEND_NOT_FULL);

        //1. 向第三方资金托管平台发送放款请求并获取返回数据
        Map<String, Object> param = new HashMap<>();
        param.put("agentId", HfbConst.AGENT_ID);
        param.put("agentProjectCode", lend.getLendNo());
        String loanNo = LendNoUtils.getLoanNo();
        param.put("agentBillNo", loanNo);
        //平台服务费用
        //月年化
        BigDecimal monthRate = lend.getServiceRate().divide(BigDecimal.valueOf(12), 8, BigDecimal.ROUND_HALF_DOWN);
        BigDecimal mchFee = lend.getInvestAmount().multiply(monthRate).multiply(BigDecimal.valueOf(lend.getPeriod()));
        param.put("mchFee", mchFee);
        param.put("note", "发起放款");
        param.put("timestamp", RequestHelper.getTimestamp());
        param.put("sign", RequestHelper.getSign(param));

        JSONObject jsonObject = RequestHelper.sendRequest(param, HfbConst.MAKE_LOAN_URL);
        String resultCode = jsonObject.getString("resultCode");
        //      0000=放款成功
        //      E100=商户授权错误
        //      E101=签名错误
        //      E103=时间戳无效
        //      E104=参数不全或不合法
        //      E105=接收放款失败
        //      U999=未知错误
        Assert.isTrue("0000".equals(resultCode), ResponseEnum.MAKE_LOAN_ERROR);
        //放款成功
        BigDecimal voteAmt = jsonObject.getBigDecimal("voteAmt");
        //2. 修改标的状态
        lend.setRealAmount(mchFee);
        lend.setStatus(LendStatusEnum.PAY_RUN.getStatus());
        lend.setPaymentTime(LocalDateTime.now());
        this.updateById(lend);

        //3. 修改借款人的账户信息
        UserInfo borrowUser = userInfoService.getById(lend.getUserId());
        userAccountService.updateUserAccount(borrowUser.getBindCode(), voteAmt, BigDecimal.ZERO);

        //4. 生成借款人交易流水
        TransFlow borrowTransFlow = new TransFlow();
        borrowTransFlow.setUserId(borrowUser.getId());
        borrowTransFlow.setUserName(borrowUser.getName());
        borrowTransFlow.setTransNo(LendNoUtils.getTransNo());
        borrowTransFlow.setTransType(TransTypeEnum.BORROW_BACK.getTransType());
        borrowTransFlow.setTransAmount(voteAmt);
        borrowTransFlow.setMemo(String.format("借款放款到账，编号：%s", lend.getLendNo()));

        //5. 根据标的id查询状态为已支付的投资项
        List<LendItem> lendItems = lendItemService.getLendItems(lend.getId(), 1);
        for (LendItem lendItem : lendItems) {
            //6. 修改投资人账户信息（转出冻结资金）
            UserInfo investUser = userInfoService.getById(lendItem.getInvestUserId());
            userAccountService.updateUserAccount(investUser.getBindCode(), BigDecimal.ZERO, lendItem.getInvestAmount().negate());

            //7. 生成投资人交易流水
            TransFlow investTransFlow = new TransFlow();
            investTransFlow.setUserId(investUser.getId());
            investTransFlow.setUserName(investUser.getName());
            investTransFlow.setTransNo(LendNoUtils.getTransNo());
            investTransFlow.setTransType(TransTypeEnum.INVEST_UNLOCK.getTransType());
            investTransFlow.setTransAmount(lendItem.getInvestAmount());
            borrowTransFlow.setMemo(String.format("冻结资金转出，出借放款，编号：%s", lend.getLendNo()));
        }

        //放款成功生成还款计划及回款计划
        this.repaymentPlan(lend);
    }

    /**
     *  获取借款人的还款计划表
     *
     * 借款人 --> 多个还款计划（分期数）
     *              |
     *              |
     *              '----> 单个还款计划 ------------> 多个回款计划（投资人数）
     *
     *
     * @param lend
     */
    private void repaymentPlan(Lend lend) {
        //还款计划表
        List<LendReturn> lendReturnList = new ArrayList<>();
        int period = lend.getPeriod();
        for (int i = 1; i <= period; i++) {
            LendReturn lendReturn = new LendReturn();
            lendReturn.setLendId(lend.getId());
            lendReturn.setBorrowInfoId(lend.getBorrowInfoId());
            lendReturn.setReturnNo(LendNoUtils.getReturnNo());
            lendReturn.setUserId(lend.getUserId());
            lendReturn.setAmount(lend.getAmount());
            //计息本金额
            lendReturn.setBaseAmount(lend.getInvestAmount());
            lendReturn.setCurrentPeriod(i);
            lendReturn.setLendYearRate(lend.getLendYearRate());
            lendReturn.setReturnMethod(lend.getReturnMethod());
            // 说明：还款计划中的这三项 = 回款计划中对应的这三项和：因此需要先生成对应的回款计划
            lendReturn.setPrincipal(BigDecimal.ZERO);
            lendReturn.setInterest(BigDecimal.ZERO);
            lendReturn.setTotal(BigDecimal.ZERO);

            lendReturn.setFee(BigDecimal.ZERO); //手续费
            lendReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));
            lendReturn.setOverdue(false);   //是否逾期
            lendReturn.setLast(i == period);
            lendReturn.setStatus(0);    //未归还

            lendReturnList.add(lendReturn);
        }
        lendReturnService.saveBatch(lendReturnList);

        // 期数 -->  还款计划id
        Map<Integer, Long> lendReturnIdMap = lendReturnList.stream().collect(Collectors.toMap(LendReturn::getCurrentPeriod, LendReturn::getId));

        // 获取标的下的标的项（每个标的对应多个标的项）
        List<LendItem> lendItems = lendItemService.getLendItems(lend.getId(), 1);
        //遍历每个标的项从而获取整个回款计划表
        List<LendItemReturn> lendItemReturnList = lendItems.stream().flatMap(lendItem -> this.returnInvest(lendItem.getId(), lendReturnIdMap, lend).stream()).collect(Collectors.toList());

        //根据生成的回款计划表重新设置还款计划的本金、利息、本息
        for (LendReturn lendReturn : lendReturnList) {
            //本金
            BigDecimal principal = lendItemReturnList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().equals(lendReturn.getId()))
                    .map(LendItemReturn::getPrincipal)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            lendReturn.setPrincipal(principal);

            //利息
            BigDecimal interest = lendItemReturnList.stream()
                    .filter(lendItemReturn -> lendItemReturn.getLendReturnId().equals(lendReturn.getId()))
                    .map(LendItemReturn::getInterest)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            lendReturn.setInterest(interest);

            //本息
            lendReturn.setTotal(principal.add(interest));
        }

        lendReturnService.updateBatchById(lendReturnList);

    }

    /**
     * 获取投资人的回款计划表
     *
     * 投资人 --> 多个回款计划（分期数）
     *
     *
     * @param lendItemId        标的项id
     * @param lendReturnIdMap   期数 --> 期数对应还款id
     * @param lend              标的
     * @return
     */
    private List<LendItemReturn> returnInvest(Long lendItemId, Map<Integer, Long> lendReturnIdMap, Lend lend) {
        LendItem lendItem = lendItemService.getById(lendItemId);
        BigDecimal investAmount = lendItem.getInvestAmount();
        BigDecimal lendYearRate = lendItem.getLendYearRate();
        int period = lend.getPeriod();

        //根据还款方式获取每期的本金、利息
        ReturnHandler returnHandler = ReturnHandlerFactory.getReturnHandler(lend.getReturnMethod().toString());
        //                                                                                  投资金额                    年利率                 期数
        Map<Integer, BigDecimal> perMonthInterest = returnHandler.getPerMonthInterest(investAmount, lendYearRate, period);      //每期利息
        Map<Integer, BigDecimal> perMonthPrincipal = returnHandler.getPerMonthPrincipal(investAmount, lendYearRate, period);    //每期本金

        //单个标的项对应一个回款计划表
        List<LendItemReturn> lendItemReturnList = new ArrayList<>();
        for (int i = 1; i <= period; i++) {
            LendItemReturn lendItemReturn = new LendItemReturn();
            lendItemReturn.setLendReturnId(lendReturnIdMap.get(i));   //标的还款id
            lendItemReturn.setLendItemId(lendItemId);
            lendItemReturn.setLendId(lend.getId());
            lendItemReturn.setInvestUserId(lendItem.getInvestUserId());
            lendItemReturn.setInvestAmount(lendItem.getInvestAmount());
            lendItemReturn.setCurrentPeriod(i);
            lendItemReturn.setLendYearRate(lendItem.getLendYearRate());
            lendItemReturn.setReturnMethod(lend.getReturnMethod());

            BigDecimal principal = perMonthPrincipal.get(i);
            BigDecimal interest = perMonthInterest.get(i);
            //最后一期应还本金 = 用当前投资人的总投资金额 - 除了最后一期前面期数计算出来的所有的应还本金
            //本金
            if(i == period) {
                BigDecimal hasPay = lendItemReturnList.stream().map(LendItemReturn::getPrincipal).reduce(BigDecimal.ZERO, BigDecimal::add);
                lendItemReturn.setPrincipal(investAmount.subtract(hasPay));
                principal = lendItemReturn.getPrincipal();
            }else {
                lendItemReturn.setPrincipal(principal);
            }
            //利息
            lendItemReturn.setInterest(interest);
            //本息
            lendItemReturn.setTotal(principal.add(interest));

            lendItemReturn.setFee(BigDecimal.ZERO);
            lendItemReturn.setReturnDate(lend.getLendStartDate());
            lendItemReturn.setReturnDate(lend.getLendStartDate().plusMonths(i));
            lendItemReturn.setOverdue(false);       //是否逾期
            lendItemReturn.setStatus(0);

            lendItemReturnList.add(lendItemReturn);
        }

        lendItemReturnService.saveBatch(lendItemReturnList);

        return lendItemReturnList;
    }
}
